package gotnames;

import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;

import com.medallia.tiny.Clock;
import com.medallia.tiny.Empty;
import com.medallia.tiny.Format;

/**
 * Various static utility functions.
 */
public class Utils {
	
	/** @return a {@link Logger} configured with the class name of the caller (see {@link #myCaller()} */
	public static Logger getLog() {
		try {
			return Logger.getLogger(Class.forName(myCaller().getClassName()).getName());
		} catch (ClassNotFoundException e) {
			throw new IllegalStateException(e);
		}
	}

	/** @return the {@link StackTraceElement} corresponding to the method calling this method */
	public static StackTraceElement myCaller() {
		// 0 here, 1 our caller, 2 their caller
		try {
			StackTraceElement st[] = new Throwable().getStackTrace();
			// hack: skip synthetic caster methods
			if (st[2].getLineNumber() == 1) return st[3];
			return st[2];
		} catch (Throwable t) {
			return new StackTraceElement("[unknown]","-","-",0);
		}
	}
	
	/** @return {@link Format#formatPretty(long, TimeUnit)} with the difference between now and the given time */
	public static String getAge(Date createdAt) {
		return Format.formatPretty(Clock.timeMillis() - createdAt.getTime(), TimeUnit.MILLISECONDS);
	}

	/** @return a string giving the number of milliseconds from the given {@link System#nanoTime()} value to the current one */
	public static String elapsed(long beginNanos) {
		return TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - beginNanos) + " ms";
	}

	/** @return the given object casted to the given type; used to avoid '@SuppressWarnings' in multiple locations  */
	@SuppressWarnings("unchecked")
	public static <X> X cast(Object object) {
		return (X) object;
	}

	/** @return same as {@link List#subList(int, int)}, but restricted to the size of the list */
	public static <X> List<X> subList(int maxElems, List<X> list) {
		return list.subList(0, Math.min(list.size(), maxElems));
	}
	
	/**
	 * @return all elements of each input collection inside one large collection.
	 */
	public static <X> List<X> flatten(Collection<? extends Collection<? extends X>> collections) {
		int size = 0;
		for (Collection<? extends X> c : collections)
			size += c.size();
		List<X> result = Empty.list(size);
		for (Collection<? extends X> c : collections)
			result.addAll(c);
		return result;
	}

	/** Remove elements from the end of the given list until it has no more than 'n' elements */
	public static void trim(int n, List<?> list) {
		n = list.size() - n;
		while (n-- > 0) {
			list.remove(list.size() - 1);
		}
	}
	
	/** @return An escaped version of the given string, in application/x-www-form-urlencoded format. */
	public static String urlEscape(String string) {
		try {
			return URLEncoder.encode(string, "utf-8");
		} catch (UnsupportedEncodingException ex) {
			throw new AssertionError(ex);
		}
	}

	/** @return An unescaped version of the given application/x-www-form-urlencoded string. */
	public static String urlUnescape(String string) {
		try {
			return URLDecoder.decode(string, "utf-8");
		} catch (UnsupportedEncodingException ex) {
			throw new AssertionError(ex);
		}
	}
	
	/** @return a list with the integers between low (inclusive) and high (exclusive) */
	public static List<Integer> range(int low, int high) {
		List<Integer> l = Empty.list();
		for (int i = low; i < high; i++)
			l.add(i);
		return l;
	}
	
	/** @return {@link #range(int, int)} with 0 as the low value */
	public static List<Integer> range(int high) {
		return range(0, high);
	}

	private static final SecureRandom secureRandom = new SecureRandom();
	
	/** @return a hex-encoded string with 128 random bits (from a {@link SecureRandom} */
	public static String getUniqueId() {
		return String.format("%016x%016x", secureRandom.nextLong(), secureRandom.nextLong());
	}

	/** @return the given object, or throw {@link IllegalArgumentException} with the given string as message if the object is null */
	public static <X> X notNull(X object, String msg) {
		if (object == null)
			throw new IllegalArgumentException(msg);
		return object;
	}
	
}
